package com.inspectortime.service.billing;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Restrictions;
import org.springframework.orm.hibernate3.SessionFactoryUtils;

import com.inspectortime.domain.Inspection;
import com.inspectortime.domain.billing.Invoice;
import com.inspectortime.domain.billing.InvoiceLineItem;
import com.inspectortime.domain.type.InspectionStatus;
import com.inspectortime.domain.util.InspectionInspectorCompanyIdComparator;
import com.inspectortime.repository.Factory;
import com.inspectortime.repository.Repository;
import com.inspectortime.util.CalendarUtil;

public class BillingService {
	
	private static Log log = LogFactory.getLog(BillingService.class);
	
	private static final double INVOICE_LINE_ITEM_AMOUNT = 5;
	
	private SessionFactory sessionFactory;
	private Repository repository;
	private Factory factory;
	
	public BillingService(Factory factory, Repository repository, SessionFactory sessionFactory) {
		this.factory = factory;
		this.repository = repository;
		this.sessionFactory = sessionFactory;
	}

	public String createInvoicesForLastMonth() {
		Calendar cal = Calendar.getInstance();
		cal.add(Calendar.MONTH, -1);
		return this.createInvoicesForMonthOf(cal.getTime());
	}
	
	public String createInvoicesForMonthOf(Date date) {
		Calendar cal = Calendar.getInstance();
		cal.setTime(date);
		cal.set(Calendar.DATE, 1);
		CalendarUtil.removeTime(cal);
		Date startDate = cal.getTime();
		
		// Last millis on the month
		cal.add(Calendar.MONTH, 1);
		cal.add(Calendar.DATE, -1);
		cal.set(Calendar.HOUR_OF_DAY, 23);
		cal.set(Calendar.MINUTE, 59);
		cal.set(Calendar.SECOND, 59);
		cal.set(Calendar.MILLISECOND, 999);
		Date endDate = cal.getTime();
		return this.createInvoicesForDates(startDate, endDate);
	}	
	
	public String createInvoicesForDates(Date start, Date end) {
		int foundInspectionCount, skippedInspectionCount=0, invoicedInspectionCount=0, invoiceCount=0; 
		
		Session session = SessionFactoryUtils.getSession(sessionFactory, true);
		Criteria crit = session.createCriteria(Inspection.class);
		crit.add(Restrictions.gt("inspectionTime", start));
		crit.add(Restrictions.le("inspectionTime", end));
		crit.add(Restrictions.eq("inspectionStatus", InspectionStatus.SCHEDULED));
		crit.add(Restrictions.eq("webBooked", Boolean.TRUE));
		List<Inspection> inspections = crit.list();
		foundInspectionCount = inspections.size();

		// Remove inspections that we've already created an invoice line for
		List<Serializable> skipIds = new ArrayList<Serializable>();
		for (Inspection inspection : inspections) {
			InvoiceLineItem skipLine = repository.findInvoiceLineItemByInspectionId((String) inspection.getId());
			if (skipLine != null) {
				skipIds.add(skipLine.getInspectionId());	
				skippedInspectionCount++;
			}			
		}
		
		// Sort them by company
		Collections.sort(inspections, new InspectionInspectorCompanyIdComparator());
		Serializable lastCompanyId = null;
		Invoice invoice = null;
		InvoiceLineItem line = null;
		for (Inspection inspection : inspections) {
			
			if (skipIds.contains(inspection.getId())) {
				continue;
			}
			
			// Create one invoice per company
			if (!inspection.getInspector().getCompany().getId().equals(lastCompanyId)) {
				if (invoice != null) {
					
					// save the previous one
					invoice.save();
				}
				invoice = factory.createInvoice();
				invoice.setCompanyId(inspection.getInspector().getCompany().getId().toString());
				invoice.setInvoicePeriodStartDate(start);
				invoice.setInvoicePeriodEndDate(end);
				invoiceCount++;
			}
			
			// Create one line item per inspection
			line = factory.createInvoiceLineItem();
			invoice.getLineItems().add(line);
			line.setInspectionId((String) inspection.getId());
			line.setInspectionTime(inspection.getInspectionTime());
			line.setInspectionAddress(inspection.getProperty().getLocation().asOneLine());
			line.setInspectorName(inspection.getInspector().getName().asFullName());
			line.setAmount(INVOICE_LINE_ITEM_AMOUNT);
			invoicedInspectionCount++;
			
			lastCompanyId = (String) inspection.getInspector().getCompany().getId();
		}
		
		// save the last one
		if (invoice != null) {
			invoice.save();	
		}		
		
		String result = "Found " + foundInspectionCount + " web-booked inspections from " + start + " to " + end + ". Created invoice line items for " + invoicedInspectionCount + " of those inspections, skipped " + skippedInspectionCount + ". Created a total of " + invoiceCount + " invoices.";
		log.info(result);
		return result;
	}
	
}
